namespace XTCast.Mp3 {
	using System;
	using System.Text;
	using XTCast.Mp3;
	using System.IO;
	using System.Collections;
	using System.Xml.Serialization;
	using System.Runtime.Serialization;
	
	/// <summary>Provides access to MP3 metadata, including MPEG data such as bitrate and length,
	/// and ID3 tags such as track title and artist (using the Tags collection).
	/// Supports MPEG level 1 and 2, layers 1-3;
	/// </summary>
	
	public enum MetaDataType{
		Filename,
		Serialization,
		ID3Tags,
	}
	
	[Serializable]
	public class MP3File:ISerializable,IComparable {

		private String _FullName;
		private string _Title;
		private string _Artist;
		private string _Album;
		private string _Genre;
		private MetaDataType _MetaDataSrc, _UseMetaData;
		private int _ID;
		private ID3TagCollection _tags;
		private bool _mpegRead; 		/// <summary> First Mpeg Frame Read </summary>
		private MP3Frame _header;	/// <summary> First Frame Header of MPEG Data </summary>
		private TimeSpan _length;
		private long _lengthBytes;
		private bool _dirty=false;

		public MP3File(System.String path, MetaDataType mdSrc) {
			_ID = GetID(path);
			_FullName=path;
			_MetaDataSrc=mdSrc;

			_Artist=this.ArtistNoTag;
			_Title=this.TitleNoTag;	

			switch (mdSrc) {
				case MetaDataType.ID3Tags:
					ReadTags();
					break;
				case  MetaDataType.Serialization:
					throw new NotSupportedException("Serializaion is not a specifiable metadata source");
					break;
			}
		}

		public MP3File() {}

        public MP3File(System.String path): this(path, MetaDataType.Filename) {}
		
		public MP3File(System.IO.FileInfo file): this(file.FullName) {}
		
		public MP3File(MP3File file):this(file._FullName){}
		
		public MP3File(System.IO.FileInfo dir, System.String name):this(dir.FullName + "\\" + name) {}

		public MP3File(SerializationInfo info, StreamingContext context) {
			_FullName = info.GetString("FullName");
			_Title = info.GetString("Title");
			_Artist = info.GetString("Artist");
			_Album = info.GetString("Album");
			_Genre = info.GetString("Genre");
			_MetaDataSrc=MetaDataType.Serialization;
			_ID = GetID(_FullName);
		}

		public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
			info.AddValue("FullName",FullName);
			info.AddValue("Title",Title);
			info.AddValue("Artist",Artist);
			info.AddValue("Album",Album);
			info.AddValue("Genre",Genre);
		}

		static Hashtable _Paths = Hashtable.Synchronized(new Hashtable());
		static int GetID(string path) {
			object o = _Paths[path];
			if (o != null) {
				return (int)o;
			} else {
				int id=_Paths.Count+1;
				_Paths[path]=id;
				return id;
			}
		}

		public int ID {
			set {
				_ID= value;
			}
			get {
				return _ID;
			}
		}

		public MetaDataType UseMetaData{
			get{
				return _UseMetaData;
			}
			set{
				_UseMetaData=value;
			}
		}

		public bool Exists {
			get {
				return File.Exists(_FullName);
			}
		}

		public void Delete() {
			File.Delete(_FullName);
		}

		public string FullName {
			get {
				return _FullName;
			}
		}

		override public String ToString() {
			return _Artist + " - " + _Title;
		}

		public string Name {
			get {
				return Path.GetFileName(_FullName);
			}
		}

		public void ReadTags() {
			if (_tags != null) return;
			_tags = new ID3TagCollection(FullName);
			lock (_tags) {
				_tags.getTag(ID3Tag.kTagTitle,ref _Title);
				if (_tags.hasTag(ID3Tag.kTagArtist))
					_tags.getTag(ID3Tag.kTagArtist,ref _Artist);
				else
					_tags.getTag(ID3Tag.kTagBand,ref _Artist);
				_tags.getTag(ID3Tag.kTagAlbum,ref _Album);
				_tags.getTag(ID3Tag.kTagGenre,ref _Genre);
				_MetaDataSrc=MetaDataType.ID3Tags;
			}
		}

		public bool Dirty {
			get {
				return _dirty;
			}
			set {
				_dirty=value;
				if (value) {
					_tags=null;
					_mpegRead=false;
				}
			}
		}

		public ID3TagCollection Tags{
			get {
				if (_tags==null) ReadTags();
					return _tags;
				}
		}

		private void readMpegData() {
			lock(this) {
				if (_mpegRead) return;
				
				System.IO.FileStream io = new FileStream(this.FullName,FileMode.Open,FileAccess.Read,FileShare.ReadWrite);
				try {
					readMP3Info(io);			
					_mpegRead = true;
				}
				finally {
					try {
						io.Close();
					}
					catch (System.IO.IOException x) {
					}
				}
			}
		}

		/// <summary>Returns the <i>approximate</i> length in seconds of the audio.
		/// This is computed based on the bitrate and the length of the entire file,
		/// so it can be significantly off if there is a lot of data in the file
		/// other than actual MPEG frames (e.g. lengthy ID3 tags.)
		/// </summary>
		
		public virtual TimeSpan Length {
			get {
				try {
					readMpegData();
					return _length;
				}
				catch (System.Exception e) {
				}
				return TimeSpan.Zero;
			}
		}

		public virtual long LengthBytes {
			get {
				try {
					readMpegData();
					return _lengthBytes;
				}
				catch (System.Exception e) {
				}
				return 0;
			}
		}

		/// <summary>Returns the MPEG level -- MP3 is level 1. </summary>
		public virtual int MPEGLevel {
			get {
				readMpegData();
				return _header.MPEGLevel;
			}			
		}
		
		/// <summary>Returns the MPEG layer -- MP3 is layer 3.</summary>
		public virtual int MPEGLayer {
			get {
				readMpegData();
				return _header.MPEGLayer;
			}			
		}		
		
		/// <summary>Returns the bitrate (bits per second) of the audio data.
		/// This will of course not be valid for variable-bit-rate files.</summary>
		public virtual int BitRate {
			get {
				readMpegData();
				return _header.BitRate;
			}
		}		
		
		/// <summary>Returns the sample rate (samples per second) of the data.</summary>
		public virtual int SampleRate {
			get {
				readMpegData();
				return _header.SampleRate;
			}
		}

		public virtual ID3Ver ID3Version {
			get {
				return Tags.ID3Version;
			}
			set {
				Tags.ID3Version=value;
			}
		}

		public virtual System.String TitleNoTag {
			get {
				System.String fname = Path.GetFileNameWithoutExtension(Name);
				int dash = fname.IndexOf((System.Char) '-');
				if (dash <= 0) return fname.Trim();
				return fname.Substring(dash + 1).Trim();
			}	
		}

		public virtual System.String ArtistNoTag {
			get {
				int dash = Name.IndexOf((System.Char) '-');
				if (dash <= 0) return null;
				System.String artist = Name.Substring(0,dash);
				return artist.Trim();
			}
		}

		public virtual System.String Title {
			get {
				if (_UseMetaData!=_MetaDataSrc & _UseMetaData==MetaDataType.ID3Tags) ReadTags();
				return _Title;
			}	
			set {
				Tags.setTag(ID3Tag.kTagTitle,value);
				_Title=value;
			}
		}

		public virtual System.String Artist {
			get {
				if (_UseMetaData!=_MetaDataSrc & _UseMetaData==MetaDataType.ID3Tags) ReadTags();
				return _Artist;
			}			
			set {
				Tags.setTag(ID3Tag.kTagArtist,value);
				_Artist=value;
			}
		}
		public virtual System.String Album {
			get {
				if (_UseMetaData!=_MetaDataSrc & _UseMetaData==MetaDataType.ID3Tags) ReadTags();
				return _Album;
			}
			set {
				Tags.setTag(ID3Tag.kTagAlbum,value);
				_Album=value;
			}
		}

		public virtual System.String Genre {
			get {
				if (_UseMetaData!=_MetaDataSrc & _UseMetaData==MetaDataType.ID3Tags) ReadTags();
				return _Genre;
			}
			set {
				Tags.setTag(ID3Tag.kTagGenre,value);
				_Genre=value;
			}
		}

		public virtual System.String Year {
			get {
				try {
					return Tags.getTag(ID3Tag.kTagYear);
				}
				catch (System.Exception e) {
					Log.Error(new MP3Exception("Error reading year tag in of " + this.Name,e));
				}
				return "";
			}
		}
		
		public virtual System.String Comment {
			get {
				try {
					return Tags.getTag(ID3Tag.kTagComment);
				}
				catch (System.Exception e) {
					Log.Error(new MP3Exception("Error reading comment tag of " + this.Name , e));
				}
				return "";
			}
		}

		public long StartingOffset {
			get {
				try {
					ReadTags();
					return Tags.EndingOffset+1;
				}
				catch (System.Exception e) {
					Log.Error(new MP3Exception("Error reading offset of " + this.Name , e));
				}
				return 0;
			}
		}
		
		/// <summary>Checks the filename suffix to determine whether the given file appears to be an
		/// MP3 file. It does <i>not</i> look at the contents of the file. </summary>
		public static bool isMP3FileType(String file) {
			if (file.ToLower().EndsWith(".mp3"))
				return true;
			return false;
		}
		
		/// <summary>Checks the filename suffix (and filetype, if on Mac OS)
		/// to determine whether this file appears to be an MP3 file.
		/// It does <i>not</i> look at the contents of the file. </summary>
		public virtual bool isMP3FileType() {
			return isMP3FileType(this.FullName);
		}
		

		//MPEG INTERNALS:		
		private void  readMP3Info(System.IO.FileStream io) {
			io.Seek(0, System.IO.SeekOrigin.Begin);

			_header = new MP3Frame();
			if (!_header.ReadFrame(io)) throw new MP3Exception("No valid mpeg frames found");
			_length = computeLength(io);
			_lengthBytes = io.Length;
		}
		
		private TimeSpan computeLength(System.IO.FileStream io) {
			if (_header.IsVbrFrame) {
				int totalFrames = _header.VbrTotalFrames();
				if (totalFrames>0) return TimeSpan.FromMilliseconds(totalFrames * MP3Frame.MillisLength);
			}
			long dataSize = io.Length - (io.Position - 3);
			if (_header.BitRate > 0)
				return TimeSpan.FromSeconds(dataSize * 8 / _header.BitRate);
			else
				return TimeSpan.Zero;
		}
		
		public override int GetHashCode() {
			if (FullName==null) return 0;
			return FullName.GetHashCode();
		}

		public int CompareTo(object obj) {
			if (!typeof(MP3File).IsInstanceOfType(obj))
				throw new System.ArgumentException("Object must be of type MP3File");
			return CompareTo((MP3File)obj);
		}

		public int CompareTo(MP3File obj) {
			return FullName.CompareTo(obj.FullName);
		}
	}
		
}